/*
    Copyright 2008, 2009, 2010 Brightworks, Inc.

    This file is part of Language Mentor.

    Language Mentor is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Language Mentor is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Language Mentor.  If not, see <http://www.gnu.org/licenses/>.
*/
package com.langcollab.languagementor.controller.download
{


    import com.brightworks.error.BwErrorEvent;
    import com.brightworks.util.Debug;
    import com.brightworks.util.IPercentCompleteReporter;
    import com.brightworks.util.Utils_String;
    import com.brightworks.util.audio.FileSetMP3DataExtractor;
    import com.brightworks.util.audio.FileSetMP3Saver;
    import com.brightworks.util.audio.MP3FileInfo;
    import com.brightworks.util.download.FileDownloader;
    import com.langcollab.languagementor.constant.Constant_Misc;
    import com.langcollab.languagementor.controller.download.DownloadProcessErrorReport;
    import com.langcollab.languagementor.model.MainModel;
    import com.langcollab.languagementor.util.Utils_LangCollab;
    import com.langcollab.languagementor.vo.ChunkFileVO;
    import com.langcollab.languagementor.vo.ChunkVO;
    import com.langcollab.languagementor.vo.ModuleVersionNativeLanguageVO;
    import com.langcollab.languagementor.vo.ModuleVersionTagVO;
    import com.langcollab.languagementor.vo.ModuleVersionTargetLanguageVO;
    import com.langcollab.languagementor.vo.ModuleVersionVO;

    import deng.fzip.FZip;
    import deng.fzip.FZipErrorEvent;
    import deng.fzip.FZipFile;

    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.filesystem.File;
    import flash.utils.ByteArray;
    import flash.utils.Dictionary;

    import mx.collections.ArrayCollection;
    import mx.collections.Sort;
    import mx.collections.SortField;

    public class DownloadProcess extends EventDispatcher implements IPercentCompleteReporter
    {
        public static const STATUS_COMPLETE:String                         = "complete";
        public static const STATUS_DOWNLOADING:String                      = "downloading";
        public static const STATUS_EXTRACTING_MP3_DATA:String              = "extracting_mp3_data";
        public static const STATUS_FAILED:String                           = "failed";
        public static const STATUS_SAVING:String                           = "saving";
        public static const STATUS_UNZIPPING:String                        = "unzipping";

        public var contentProviderId:String;
        public var downloadFileExtension:String;
        public var downloadURLRoot:String;
        public var errorReport:DownloadProcessErrorReport;
        public var model:MainModel;
        public var publishedModuleVersionId:String;
        public var repeatedAttemptsAllowed:int;
        public var saveFileFolder:String;
        public var status:String;
        // unzippedFileDataList:
        //     - props  = file IDs
        //     - values = MP3FileInfo instances
        //     - created after zip file is unzipped - MP3FileInfo.fileName, .fileFolder and 
        //       .mp3FormattedByteData are set at this point
        //     - used in 'save files' process - data isn't changed, just saved to hard disk
        //     - used in 'extract data' process - MP3FileInfo.audioMaxVolume and .audioMilliseconds
        //       are set at this point
        public var unzippedFileDataList:Dictionary;

        private var _allowedLanguageList:Array; // iso639_3 language codes used in module - native or target
        private var _downloadedZippedFileData:ByteArray;
        private var _fileDownloader:FileDownloader;
        private var _fileSetMP3DDataExtractor:FileSetMP3DataExtractor;
        private var _fileSetMP3Saver:FileSetMP3Saver;
        private var _fileUnzipper:FZip;
        private var _unzipFailureCount:int = 0;

        // ---------------------------------------------------------
        //
        //         Getters & Setters
        //
        // ---------------------------------------------------------

        private var _moduleVersionSignature:String;
        public function get moduleVersionSignature():String {
            if (status != STATUS_COMPLETE) 
                return null;
            if (_moduleVersionSignature == null) {
                _moduleVersionSignature = Utils_LangCollab.computeModuleVersionSignature(unzippedFileDataList);
            }
            return _moduleVersionSignature;
        }

        // ---------------------------------------------------------
        //
        //         Public Methods
        //
        // ---------------------------------------------------------

        public function DownloadProcess(model:MainModel)
        {
            this.model = model;
            super();
            errorReport = new DownloadProcessErrorReport();
        }

        public function dispose():void
        {
            errorReport = null;
            model = null;
            if (_fileDownloader)
            {
                _fileDownloader.dispose();
                _fileDownloader = null;
            }
            if (_fileSetMP3DDataExtractor)
            {
                _fileSetMP3DDataExtractor.dispose();
                _fileSetMP3DDataExtractor = null;
            }
            if (_fileSetMP3Saver)
            {
                _fileSetMP3Saver.dispose();
                _fileSetMP3Saver = null;
            }
            cleanupUnzipProcess();
        }

        public function getAllVOs():Array 
        {
            var result:Array = [];
            var chunkInfoDict:Dictionary = extractChunkInfoFromUnzippedFileDataList();
            if (!chunkInfoDict)
                return null;
            result.push(getModuleVersionVO());
            result = result.concat(getModuleVersionNativeLanguageVOs(chunkInfoDict));
            result = result.concat(getModuleVersionTargetLanguageVOs(chunkInfoDict));
            result = result.concat(getModuleVersionTagVOs());
            result = result.concat(getChunkAndChunkFileVOs(chunkInfoDict));
            return result;
        }

        public function getPercentComplete():int 
        {
            if (isCompleteOrFailed()) {
                return 100;
            }
            else if (_fileDownloader.getPercentComplete() < 100) {
                return Math.floor(_fileDownloader.getPercentComplete() * .75);
            }
            else {
                return 75;
            }
        }

        public function isCompleteOrFailed():Boolean 
        {
            return ((status == STATUS_COMPLETE) || (status == STATUS_FAILED));
        }

        public function start():void 
        {
            Debug.info("DownloadProcess.start() - " + publishedModuleVersionId + " - create/start FileDownloader");
            status = STATUS_DOWNLOADING;
            _fileDownloader = new FileDownloader();
            _fileDownloader.downloadFolderURL      = downloadURLRoot + "/" + publishedModuleVersionId + "/";
            _fileDownloader.downloadFileName       = publishedModuleVersionId;
            _fileDownloader.downloadFileExtension  = downloadFileExtension;
            _fileDownloader.addEventListener(Event.COMPLETE, onDownloadComplete);
            _fileDownloader.addEventListener(BwErrorEvent.ERROR_GENERIC, onDownloadFailure);
            _fileDownloader.start();
        }

        // ---------------------------------------------------------
        //
        //         Private Methods
        //
        // ---------------------------------------------------------

        private function checkFileNames():Boolean 
        {
            var bFileNamingError:Boolean = false;
            var bUnallowedLanguageFileError:Boolean = false;
            createAllowedLanguageList();
            var fileExtensionLength:int = Constant_Misc.FILEPATHINFO_CHUNK_FILE_EXTENSION.length;
            var fileId:String;
            var languageCodeSubstring:String;
            var languageCodeSubstringEndIndex:int;
            var languageCodeSubstringStartIndex:int;
            for (fileId in unzippedFileDataList) 
            {
                if (!Utils_String.isStringEndsWith(fileId, "." + Constant_Misc.FILEPATHINFO_CHUNK_FILE_EXTENSION)) 
                {
                    bFileNamingError = true;
                    if (!errorReport.errorData_AudioFileNaming.hasOwnProperty("incorrectFileNameList")) 
                    {
                        errorReport.errorData_AudioFileNaming.incorrectFileNameList = [];
                    }
                    (errorReport.errorData_AudioFileNaming.incorrectFileNameList as Array).push(fileId);
                }
                languageCodeSubstringEndIndex = fileId.length - (fileExtensionLength + 1);
                languageCodeSubstringStartIndex = languageCodeSubstringEndIndex - 3;
                languageCodeSubstring = fileId.substring(languageCodeSubstringStartIndex, languageCodeSubstringEndIndex)
                if (_allowedLanguageList.indexOf(languageCodeSubstring) == -1) 
                {
                    bUnallowedLanguageFileError = true;
                    if (!errorReport.errorData_UnallowedLanguageFileIncluded.hasOwnProperty("incorrectFileNameList")) 
                    {
                        errorReport.errorData_UnallowedLanguageFileIncluded.incorrectFileNameList = [];
                    }
                    (errorReport.errorData_UnallowedLanguageFileIncluded.incorrectFileNameList as Array).push(fileId);
                }
            }
            if (bFileNamingError || bUnallowedLanguageFileError)
                errorReport.bErrorReported = true;
            if (bFileNamingError)
                errorReport.errorTypeList.push(DownloadProcessErrorReport.ERROR_AUDIO_FILE_NAMING);
            if (bUnallowedLanguageFileError)
                errorReport.errorTypeList.push(DownloadProcessErrorReport.ERROR_UNALLOWED_LANGUAGE_FILE_INCLUDED);
            return !(bFileNamingError || bUnallowedLanguageFileError);
        }

        private function cleanupUnzipProcess():void
        {
            if (_fileUnzipper)
            {
                _fileUnzipper.removeEventListener(Event.COMPLETE, onUnzipComplete);
                _fileUnzipper.removeEventListener(FZipErrorEvent.PARSE_ERROR, onUnzipFailure);
                _fileUnzipper = null;
            }
        }

        private function createAllowedLanguageList():void 
        {
            _allowedLanguageList = [];
            var i:int;
            var iso639_3Code:String;
            var languageNodeList:XMLList;
            var languageXML:XML;
            var moduleXML:XML = getModuleXML();
            languageNodeList = moduleXML.nativeLanguages[0].language;
            for each (languageXML in languageNodeList) {
                iso639_3Code = languageXML.iso639_3Code[0].toString();
                _allowedLanguageList.push(iso639_3Code);
            }
            languageNodeList = moduleXML.targetLanguages[0].language;
            for each (languageXML in languageNodeList) {
                iso639_3Code = languageXML.iso639_3Code[0].toString();
                i = _allowedLanguageList.indexOf(iso639_3Code);
                if (i == -1) {
                    _allowedLanguageList.push(iso639_3Code);
                }
            }
        }

        private function extractChunkInfoFromUnzippedFileDataList():Dictionary 
        {
            // Format for result:
            //		{
            //			"0000000001.000" : 	{
            //									locationInOrder : 1,
            //									languageFileData :	{
            //															cmn: 	{
            //																		duration:	  2031,
            //																		languageCode: "cmn",
            //																		maxVolume:	  .64832
            //																	},
            //															eng: 	{
            //																		duration:	  1883,
            //																		languageCode: "eng",
            //																		maxVolume:	  .89021
            //																	}
            //														}
            //								},
            //			"0000000001.001" : 	{
            //									locationInOrder : 2,
            //									languageFileData :	{
            //															cmn: 	{
            //																		duration:	  3290,
            //																		languageCode: "cmn",
            //																		maxVolume:	  .55970
            //																	},
            //															eng: 	{
            //																		duration:	  3522,
            //																		languageCode: "eng",
            //																		maxVolume:	  .73039
            //																	}
            //														}
            //								},
            //			etc...
            //
            //
            // 	alphabetizedChunkFileNameRootList is a temp list that looks like this:
            //								[
            //									"0000000001.000",
            //									"0000000001.001",
            //									"0000000001.002",
            //									etc...
            //								],

            var result:Dictionary = new Dictionary();
            var alphabetizedChunkFileNameRootList:ArrayCollection = new ArrayCollection();
            var fileId:String;
            var fileNameRoot:String;
            var sort:Sort = new Sort();
            sort.fields = [new SortField(null, true)];
            alphabetizedChunkFileNameRootList.sort = sort;
            alphabetizedChunkFileNameRootList.refresh();
            for (fileId in unzippedFileDataList) {
                // File names are checked after unzipping in checkFileNames()
                fileNameRoot = Utils_String.removeCharsFromEndOfString(fileId, Constant_Misc.FILEPATHINFO_CHUNK_FILE_LANGUAGE_CODE_LENGTH + Constant_Misc.FILEPATHINFO_CHUNK_FILE_EXTENSION.length + 2);
                if (!alphabetizedChunkFileNameRootList.contains(fileNameRoot)) {
                    alphabetizedChunkFileNameRootList.addItem(fileNameRoot);
                }
            }
            var chunkCount:int = alphabetizedChunkFileNameRootList.length;
            var chunkInfoObject:Object;
            var currentChunk:int;
            for (currentChunk = 0; currentChunk < chunkCount; currentChunk++) {
                fileNameRoot = alphabetizedChunkFileNameRootList[currentChunk];
                chunkInfoObject =	{
                        locationInOrder : currentChunk + 1,
                        languageFileData : {}
                }
                result[fileNameRoot] = chunkInfoObject;
            }
            var languageCode:String;
            var languageFileData:Object;
            for (fileId in unzippedFileDataList) {
                // File names are checked after unzipping in checkFileNames()
                var oneFilesData:MP3FileInfo = unzippedFileDataList[fileId];
                fileNameRoot = Utils_String.removeCharsFromEndOfString(fileId, Constant_Misc.FILEPATHINFO_CHUNK_FILE_LANGUAGE_CODE_LENGTH + Constant_Misc.FILEPATHINFO_CHUNK_FILE_EXTENSION.length + 2);
                languageCode = extractLanguageCodeFromFullFileName(fileId);
                if (!oneFilesData.audioMilliseconds is int)
                    return null;
                if (!oneFilesData.audioMaxVolume is Number)
                    return null;
                languageFileData = result[fileNameRoot].languageFileData;
                languageFileData[languageCode] =	{
                        duration     : oneFilesData.audioMilliseconds,
                        languageCode : languageCode,
                        maxVolume    : oneFilesData.audioMaxVolume
                }
            }
            return result;
        }

        private function extractLanguageCodeFromFullFileName(fileName:String):String 
        {
            var startIndex:int = fileName.length - (Constant_Misc.FILEPATHINFO_CHUNK_FILE_LANGUAGE_CODE_LENGTH + Constant_Misc.FILEPATHINFO_CHUNK_FILE_EXTENSION.length + 1);
            var endIndex:int = fileName.length - (Constant_Misc.FILEPATHINFO_CHUNK_FILE_EXTENSION.length + 1);
            var result:String = fileName.substring(startIndex, endIndex);
            return result;
        }

        private function getChunkAndChunkFileVOs(chunkInfoDict:Dictionary):Array 
        {
            if (status != STATUS_COMPLETE) 
                Debug.error_Fatal("DownloadProcess.getChunkVOs(): Download process not complete");
            var result:Array = [];
            var chunkInfo:Object;
            var chunkVO:ChunkVO;
            var chunkFileVO:ChunkFileVO;
            var fileNameRoot:String;
            var languageCode:String;
            var languageFileInfo:Object;
            for (fileNameRoot in chunkInfoDict) {
                chunkInfo = chunkInfoDict[fileNameRoot];
                chunkVO = new ChunkVO();
                result.push(chunkVO);
                chunkVO.contentProviderId       = contentProviderId;
                chunkVO.fileNameRoot            = fileNameRoot;
                chunkVO.locationInOrder         = chunkInfo.locationInOrder;
                chunkVO.moduleVersionSignature  = moduleVersionSignature;
                for each (languageFileInfo in chunkInfo.languageFileData) {
                    chunkFileVO = new ChunkFileVO();
                    result.push(chunkFileVO);
                    languageCode = languageFileInfo.languageCode;
                    chunkFileVO.chunkLocationInOrder    = chunkInfo.locationInOrder;
                    chunkFileVO.contentProviderId       = contentProviderId;
                    chunkFileVO.duration                = languageFileInfo.duration;
                    chunkFileVO.iso639_3Code            = languageCode;
                    chunkFileVO.languageId              = model.dataManager.getLanguageIdFromIso639_3Code(languageCode);
                    chunkFileVO.maxVolume               = languageFileInfo.maxVolume;
                    chunkFileVO.moduleVersionSignature  = moduleVersionSignature;
                    //    \modules\some.content.provider.id\com.languagecollaborative.0000000001\
                    //                             0000000001.000.cmn.mp3
                    chunkFileVO.filePath = 	File.separator +
                        Constant_Misc.FILEPATHINFO_DOWNLOADED_MODULES_FOLDER_NAME +
                        File.separator +
                        contentProviderId +
                        File.separator +
                        fileNameRoot + "." + languageCode + "." +
                        Constant_Misc.FILEPATHINFO_CHUNK_FILE_EXTENSION;
                }
            }
            return result;
        }

        private function getModuleVersionVO():ModuleVersionVO 
        {
            if (status != STATUS_COMPLETE) {
                Debug.error_Fatal("DownloadProcess.getModuleVersionVO(): Download process not complete");
            }
            var moduleXML:XML = getModuleXML();
            var moduleVersionVO:ModuleVersionVO = new ModuleVersionVO();
            moduleVersionVO.contentProviderId        = contentProviderId;
            moduleVersionVO.publishedModuleVersionId = publishedModuleVersionId;
            moduleVersionVO.levelId                  = model.dataManager.getLevelIdFromNativeLanguageLevelLabel(moduleXML.level[0].toString());
            moduleVersionVO.moduleVersionSignature   = moduleVersionSignature;
            moduleVersionVO.uploaded                 = false;
            return moduleVersionVO;
        }

        private function getModuleVersionNativeLanguageVOs(chunkInfoDict:Dictionary):Array 
        {
            if (status != STATUS_COMPLETE) 
                Debug.error_Fatal("DownloadProcess.getModuleVersionNativeLanguageVOs(): Download process not complete");
            var result:Array = [];
            var moduleXML:XML = getModuleXML();
            var descriptionString:String;
            var iso639_3Code:String;
            var languageXML:XML;
            var vo:ModuleVersionNativeLanguageVO;
            var languageNodeList:XMLList = moduleXML.nativeLanguages[0].language;
            for each (languageXML in languageNodeList) {
                vo = new ModuleVersionNativeLanguageVO();
                iso639_3Code = languageXML.iso639_3Code[0].toString();
                vo.allChunksHaveAudioFile  = isAllChunksHaveAudioFileForLanguage(iso639_3Code, chunkInfoDict);
                vo.contentProviderId       = contentProviderId;
                vo.creditsXML              = languageXML.credits[0].toString();
                vo.iso639_3Code            = iso639_3Code;
                vo.languageId              = model.dataManager.getLanguageIdFromIso639_3Code(iso639_3Code);
                vo.moduleName              = languageXML.moduleName[0].toString();
                vo.moduleVersionSignature  = moduleVersionSignature;
                if (XMLList(languageXML.description).length() > 0) {
                    descriptionString = languageXML.description[0].toString();
                    descriptionString = processModuleDescriptionNodeForStorageInVO(descriptionString);
                    vo.description = descriptionString;
                }
                if (vo.allChunksHaveAudioFile) {
                    vo.totalAudioFileDuration  = getTotalAudioFileDurationForLanguage(iso639_3Code, chunkInfoDict);
                }
                result.push(vo);
            }
            return result;
        }

        private function getModuleVersionTagVOs():Array 
        {
            if (status != STATUS_COMPLETE) 
                Debug.error_Fatal("DownloadProcess.getModuleVersionTagVOs(): Download process not complete");
            var result:Array = [];
            var moduleXML:XML = getModuleXML();
            var tagXML:XML;
            var moduleVersionTagVO:ModuleVersionTagVO;
            if (moduleXML.hasOwnProperty("tags")) {
                for each (tagXML in XMLList(moduleXML.tags.tag)) {
                    moduleVersionTagVO = new ModuleVersionTagVO();
                    moduleVersionTagVO.contentProviderId       = contentProviderId;
                    moduleVersionTagVO.moduleVersionSignature  = moduleVersionSignature;
                    moduleVersionTagVO.tag                     = tagXML.text[0].toString();
                    moduleVersionTagVO.tagLanguageId           = model.dataManager.getLanguageIdFromIso639_3Code(tagXML.languageIso639_3Code[0].toString());
                    result.push(moduleVersionTagVO);
                }
            }
            return result;
        }

        private function getModuleVersionTargetLanguageVOs(chunkInfoDict:Dictionary):Array 
        {
            if (status != STATUS_COMPLETE) 
                Debug.error_Fatal("DownloadProcess.getModuleVersionTargetLanguageVOs(): Download process not complete");
            var result:Array = [];
            var moduleXML:XML = getModuleXML();
            var descriptionString:String;
            var iso639_3Code:String;
            var languageXML:XML;
            var vo:ModuleVersionTargetLanguageVO;
            var languageNodeList:XMLList = moduleXML.targetLanguages[0].language;
            for each (languageXML in languageNodeList) {
                vo = new ModuleVersionTargetLanguageVO();
                iso639_3Code = languageXML.iso639_3Code[0].toString();
                vo.allChunksHaveAudioFile  = isAllChunksHaveAudioFileForLanguage(iso639_3Code, chunkInfoDict);
                vo.contentProviderId       = contentProviderId;
                vo.iso639_3Code            = iso639_3Code;
                vo.languageId              = model.dataManager.getLanguageIdFromIso639_3Code(iso639_3Code);
                vo.moduleName              = languageXML.moduleName[0].toString();
                vo.moduleVersionSignature  = moduleVersionSignature;
                // It is questionable whether we'll ever use target-language module descriptions, but, just in case...
                if (XMLList(languageXML.description).length() < 0) {
                    descriptionString = languageXML.description[0].toString();
                    descriptionString = processModuleDescriptionNodeForStorageInVO(descriptionString);
                    vo.description = descriptionString;
                }
                if (vo.allChunksHaveAudioFile) {
                    vo.totalAudioFileDuration  = getTotalAudioFileDurationForLanguage(iso639_3Code, chunkInfoDict);
                }
                result.push(vo);
            }
            return result;
        }

        private function getModuleXML():XML 
        {
            var result:XML = XML(model.currentRepositoryXML.modules.module.(publishedModuleVersionId.toString() == this.publishedModuleVersionId)[0]).copy();
            return result;
        }

        private function getTotalAudioFileDurationForLanguage(iso639_3Code:String, chunkInfoDict:Dictionary):int 
        {
            var result:int = 0;
            var chunkInfo:Object;
            for each (chunkInfo in chunkInfoDict) {
                result += chunkInfo.languageFileData[iso639_3Code].duration;
            }
            return result;
        }

        private function isAllChunksHaveAudioFileForLanguage(iso639_3Code:String, chunkInfoDict:Dictionary):Boolean 
        {
            var chunkInfo:Object;
            for each (chunkInfo in chunkInfoDict) {
                if (!chunkInfo.languageFileData.hasOwnProperty(iso639_3Code)) return false;
            }
            return true;
        }

        private function onDownloadComplete(event:Event):void 
        {
            Debug.info("DownloadProcess.onDownloadComplete() - " + publishedModuleVersionId + " - create/start FZip instance");
            status = STATUS_UNZIPPING;
            _downloadedZippedFileData = FileDownloader(event.target).fileData;
            startUnzipProcess();
        }

        private function onDownloadFailure(event:BwErrorEvent):void 
        {
            Debug.info("DownloadProcess.onDownloadFailure() - " + publishedModuleVersionId + " - reportFailed()");
            errorReport.bErrorReported = true;
            errorReport.errorTypeList.push(DownloadProcessErrorReport.ERROR_DOWNLOAD_FAILED);
            errorReport.errorData_DownloadFailed.fileDownloaderErrorReport = event.report;
            reportFailed();
        }

        private function onFileSetMP3DataExtractionComplete(event:Event):void 
        {
            Debug.info("DownloadProcess.onFileSetMP3DataExtractionComplete() - " + publishedModuleVersionId + " - reportComplete()");
            status = STATUS_COMPLETE;
            reportComplete();
        }

        private function onFileSetMP3DataExtractionFailure(event:BwErrorEvent):void 
        {
            Debug.info("DownloadProcess.onFileSetMP3DataExtractionFailure() - " + publishedModuleVersionId + " - reportFailed()");
            errorReport.bErrorReported = true;
            errorReport.errorTypeList.push(DownloadProcessErrorReport.ERROR_MP3_DATA_EXTRACTION);
            errorReport.errorData_MP3DataExtractionFailed.fileSetMP3DataExtractorErrorReport = event.report;
            reportFailed();
        }

        private function onFileSetSaveComplete(event:Event):void 
        {
            Debug.info("DownloadProcess.onFileSetSaveComplete() - " + publishedModuleVersionId + " - create/start FileSetMP3DataExtractor");


            /*var file:File = new File();
            file.browseForDirectory("foo");
            return;*/

            _fileSetMP3DDataExtractor = new FileSetMP3DataExtractor();
            _fileSetMP3DDataExtractor.fileData = unzippedFileDataList;
            _fileSetMP3DDataExtractor.addEventListener(Event.COMPLETE, onFileSetMP3DataExtractionComplete);
            _fileSetMP3DDataExtractor.addEventListener(BwErrorEvent.ERROR_GENERIC, onFileSetMP3DataExtractionFailure);
            _fileSetMP3DDataExtractor.start();
            status = STATUS_EXTRACTING_MP3_DATA;
        }

        private function onFileSetSaveFailure(event:BwErrorEvent):void 
        {
            Debug.info("DownloadProcess.onFileSetSaveFailure() - " + publishedModuleVersionId + " - reportFailed()");
            errorReport.bErrorReported = true;
            errorReport.errorTypeList.push(DownloadProcessErrorReport.ERROR_SAVE_FILES_TO_DISK);
            errorReport.errorData_SaveToDiskFailed.fileSetSaverErrorReport = event.report;
            reportFailed();
        }

        private function onUnzipComplete(event:Event):void 
        {
            Debug.info("DownloadProcess.onUnzipComplete() - " + publishedModuleVersionId + " - create/start FileSetMP3Saver");
            unzippedFileDataList = new Dictionary();
            var fileCount:int = _fileUnzipper.getFileCount();
            var fileId:String;
            var oneFilesInfo:MP3FileInfo;
            var i:int;
            var fzipFile:FZipFile;
            for (i = 0; i < fileCount; i++) {
                fzipFile = _fileUnzipper.getFileAt(i);
                fileId = fzipFile.filename;
                oneFilesInfo = new MP3FileInfo;
                oneFilesInfo.fileFolder = saveFileFolder;
                oneFilesInfo.fileName = fileId;
                oneFilesInfo.mp3FormattedByteData = fzipFile.content;
                unzippedFileDataList[fileId] = oneFilesInfo;
            }
            cleanupUnzipProcess();
            if (!checkFileNames()) {
                reportFailed();
                return;
            }
            _fileSetMP3Saver = new FileSetMP3Saver();
            _fileSetMP3Saver.fileData = unzippedFileDataList;
            _fileSetMP3Saver.addEventListener(Event.COMPLETE, onFileSetSaveComplete);
            _fileSetMP3Saver.addEventListener(BwErrorEvent.ERROR_GENERIC, onFileSetSaveFailure);
            _fileSetMP3Saver.start();
            status = STATUS_SAVING;
        }

        private function onUnzipFailure(event:FZipErrorEvent):void 
        {
            _unzipFailureCount++
            Debug.info("DownloadProcess.onUnzipFailure() #" + _unzipFailureCount + " - " + publishedModuleVersionId + " - reportFailed()");
            if (_unzipFailureCount <= repeatedAttemptsAllowed)
            {
                cleanupUnzipProcess();
                startUnzipProcess();
            }
            else
            {
                errorReport.errorData_UnzipFailed.errorText = event.text;
                errorReport.errorData_UnzipFailed.errorType = event.type;
                errorReport.bErrorReported = true;
                errorReport.errorTypeList.push(DownloadProcessErrorReport.ERROR_UNZIP_FAILED);
                reportFailed();
                cleanupUnzipProcess();			}
        }

        private function processModuleDescriptionNodeForStorageInVO(sNode:String):String 
        {
            // If node content contains a link, extracted string will contain the enclosing tags
            sNode = Utils_String.replaceAll(sNode, "<description>", "");
            sNode = Utils_String.replaceAll(sNode, "</description>", "");
            sNode = Utils_String.removeLineBreaks(sNode);
            // We want to add underlining to links
            sNode = Utils_String.replaceAll(sNode, "<a", "<u><a ");
            sNode = Utils_String.replaceAll(sNode, "</a>", "</a></u>");
            // Some double spaces come through
            sNode = Utils_String.replaceAll(sNode, "  ", " ");
            return sNode;
        }

        private function reportComplete():void 
        {
            status = STATUS_COMPLETE;
            dispatchEvent(new Event(Event.COMPLETE));
        }

        private function reportFailed():void 
        {
            // methods that call this method should add error info to .errorReport
            status = STATUS_FAILED;
            errorReport.contentProviderId = contentProviderId,
                errorReport.downloadFileExtension = downloadFileExtension,
                errorReport.downloadURLRoot = downloadURLRoot,
                errorReport.publishedModuleVersionId = publishedModuleVersionId
            var e:BwErrorEvent = new BwErrorEvent(BwErrorEvent.ERROR_GENERIC);
            e.report = errorReport;
            dispatchEvent(e);
        }

        private function startUnzipProcess():void
        {
            _fileUnzipper = new FZip();
            _fileUnzipper.addEventListener(Event.COMPLETE, onUnzipComplete);
            _fileUnzipper.addEventListener(FZipErrorEvent.PARSE_ERROR, onUnzipFailure);
            _fileUnzipper.loadBytes(_downloadedZippedFileData);
        }

    }
}

